package knapsackga;

import java.util.ArrayList;
import java.util.List;


/**
 * Individual This class implements the Genome Interface.
 * 
 * @author Curtis McCully
 */

public class Individual implements Genome
{
    protected Knapsack _knapsack = null;

    private FitnessFunction _fitness_function = null;

    protected int[] _alleles = null;

    private int _num_objects = 0;

    private double _fitness = Double.NEGATIVE_INFINITY;

    public Individual( Knapsack knapsack, FitnessFunction fitness_function )
    {
        _knapsack = knapsack;
        _fitness_function = fitness_function;

        // Create a randomized set of alleles using the number of objects from
        // the knapsack
        _num_objects = knapsack.getNumberOfObjects();
        _alleles = new int[_num_objects];
        for( int i = 0; i < _num_objects; i++ )
        {
            _alleles[i] = ( Math.random() < .5 ? 0 : 1 );
        }
    }

    private Individual( Knapsack knapsack,
            FitnessFunction fitness_function,
            int[] alleles,
            int num_objects )
    {
        _knapsack = knapsack;
        _fitness_function = fitness_function;
        _alleles = alleles;
        _num_objects = num_objects;
    }

    /**
     * Returns the "children" of this genome crossover with the given genome. It
     * doesn't modify this genome or the given one.
     * 
     * @param other_parent the other genome to crossover this genome with
     * @return the resulting children
     */
    public List<Genome> crossover( Genome other_parent )
    {
        ArrayList<Genome> children = new ArrayList<Genome>( 2 );

        // find the crossover points
        int index_1 = (int) ( Math.random() * _num_objects );
        int index_2 = (int) ( Math.random() * _num_objects );
        int point_1;
        int point_2;
        int[] other_alleles = other_parent.getAlleles();
        // Make sure that the crossover points are ordered smallest to largest
        if( index_1 > index_2 )
        {
            point_2 = index_1;
            point_1 = index_2;
        }
        else
        {
            point_1 = index_1;
            point_2 = index_2;
        }
        int[] new_alleles = new int[_num_objects];
        int[] new_other_alleles = new int[_num_objects];
        // go through the alleles
        for( int i = 0; i < _num_objects; i++ )
        {
            // if the alleles are between the crossover points swap the alleles
            if( i < point_2 && i > point_1 )
            {
                new_alleles[i] = other_alleles[i];
                new_other_alleles[i] = _alleles[i];

            }
            // otherwise keep the same as the parent
            else
            {
                new_alleles[i] = _alleles[i];
                new_other_alleles[i] = other_alleles[i];
            }
        }
        children.add( 0, new Individual( _knapsack, _fitness_function,
                new_alleles, _num_objects ) );
        children.add( 1, new Individual( _knapsack, _fitness_function,
                new_other_alleles, _num_objects ) );
        return children;
    }

    /**
     * Returns the fitness of this genome.
     * 
     * @return the genomes fitness
     */
    public double getFitness()
    {
        if( _fitness == Double.NEGATIVE_INFINITY )
        {
            _fitness = _fitness_function.calculateFitness( this._alleles );
        }

        return _fitness;
    }

    /**
     * Sets the fitness of this genome.
     * 
     * @param fitness
     */
    public void setFitness( double fitness )
    {
        _fitness = fitness;
    }

    /**
     * Creates an exact copy, a clone, of this genome.
     * 
     * @return a clone of this genome
     */
    public Genome makeClone()
    {
        // return a clone of yourself
        return new Individual( _knapsack, _fitness_function, _alleles,
                _num_objects );
    }

    /**
     * Returns an in-place mutated form of this genome. I.e. The mutation
     * modifies this genome.
     */
    public void mutate( double bit_mutation_probability )
    {
        // Go through each allele and mutate
        for( int i = 0; i < _num_objects; i++ )
        {
            // Test to see if we will mutate this allele
            if( Math.random() < bit_mutation_probability )
            {
                // mutate the allele
                if( _alleles[i] == 0 )
                {
                    _alleles[i] = 1;
                }
                else
                {
                    _alleles[i] = 0;
                }
            }

        }

    }

    /**
     * Returns the alleles of the genome
     */
    public int[] getAlleles()
    {

        return _alleles;
    }

    public String toString()
    {
        StringBuilder str = new StringBuilder( "[" );

        for( int i = 0; i < _num_objects; i++ )
        {
            str.append( _alleles[i] );
            if( i < _num_objects - 1 )
            {
                str.append( " " );
            }
        }
        str.append( ']' );

        return str.toString();
    }

}
